package br.unb.cic.lp.gol;

import java.security.InvalidParameterException;

/**
 * Representa um ambiente (environment) do jogo GameOfLife.
 * 
 * Essa implementacao eh nao inifinita, ou seja, nem todas as celulas possuem
 * oito celulas vizinhas. Por exemplo, a celula de coordenada (0,0) possui
 * apenas tres celulas vizinhas, (0,1), (1,0) e (1,1).
 * 
 * Um ambiente eh representado como um array bidimensional de celulas, com
 * altura (height) e comprimento (width).
 * 
 * @author rbonifacio
 */
public class GameEngine {
    private static final int CONWAY = 1;
    private static final int HIGHLIFE = 2;

    private int height;
    private int width;
    private Cell[][] cells;
    private Estrategia estrategia;
    private Statistics statistics;

    private Memento[] memento;
    private int mementoCount;

    /**
     * Construtor da classe Environment.
     * 
     * @param height
     *            dimensao vertical do ambiente
     * @param width
     *            dimentsao horizontal do ambiente
     */
    public GameEngine(int height, int width, Statistics statistics) {
        this.height = height;
        this.width = width;
        this.statistics = statistics;
        memento = new Memento[5];
        for (int i = 0; i < 5; i++) {
            memento[i] = new Memento(height, width);
        }
        cells = new Cell[height][width];

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                cells[i][j] = new Cell();
                cells[i][j].addCellListener(statistics);
            }
        }
        this.setEstrategia(2);
    }

    public void novaGeracao() {
        getEstrategia().nextGeneration();
    }

    /**
     * Torna a celula de posicao (i, j) viva
     * 
     * @param i
     *            posicao vertical da celula
     * @param j
     *            posicao horizontal da celula
     * 
     * @throws InvalidParameterException
     *             caso a posicao (i, j) nao seja valida.
     */
    public void makeCellAlive(int i, int j) throws InvalidParameterException {
        if (validPosition(i, j)) {
            cells[i][j].revive();
            // Comentado devido ao observer
            // statistics.recordRevive();
        } else {
            new InvalidParameterException("Invalid position (" + i + ", " + j + ")");
        }
    }

    /**
     * Verifica se uma celula na posicao (i, j) estah viva.
     * 
     * @param i
     *            Posicao vertical da celula
     * @param j
     *            Posicao horizontal da celula
     * @return Verdadeiro caso a celula de posicao (i,j) esteja viva.
     * 
     * @throws InvalidParameterException
     *             caso a posicao (i,j) nao seja valida.
     */
    public boolean isCellAlive(int i, int j) throws InvalidParameterException {
        if (validPosition(i, j)) {
            return cells[i][j].isAlive();
        } else {
            throw new InvalidParameterException("Invalid position (" + i + ", " + j + ")");
        }
    }

    /**
     * Retorna o numero de celulas vivas no ambiente. Esse metodo eh
     * particularmente util para o calculo de estatisticas e para melhorar a
     * testabilidade.
     * 
     * @return numero de celulas vivas.
     */
    public int numberOfAliveCells() {
        int aliveCells = 0;
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                if (isCellAlive(i, j)) {
                    aliveCells++;
                }
            }
        }
        return aliveCells;
    }

    /*
     * Verifica se uma posicao (a, b) referencia uma celula valida no tabuleiro.
     */
    private boolean validPosition(int a, int b) {
        return a >= 0 && a < height && b >= 0 && b < width;
    }

    /* Metodos de acesso as propriedades height e width */

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    /**
     * Atualiza o componente view (representado pela classe GameBoard),
     * possivelmente como uma resposta a uma atualiza�‹o do jogo.
     */
    public boolean[][] update() {
        boolean[][] celulas = new boolean[getHeight()][getWidth()];
        for (int i = 0; i < getHeight(); i++) {
            for (int j = 0; j < getWidth(); j++) {
                celulas[i][j] = isCellAlive(i, j);
            }
        }
        return celulas;
    }

    public Estrategia getEstrategia() {
        return estrategia;
    }

    public void setEstrategia(int i) {
        switch (i) {
        case CONWAY:
            estrategia = new Conway(height, width, cells);
            break;
        case HIGHLIFE:
            estrategia = new HighLife(height, width, cells);
            break;
        default:
            throw new IllegalArgumentException("Tipo de estratégia inexistente");
        }

    }

    public void saveState() {
        if (mementoCount < 5) {
            memento[mementoCount].saveState(cells, statistics);
            mementoCount++;
        } else {
            for (int i = 1; i < mementoCount; i++) {
                memento[i].saveState(memento[i - 1]);
            }
            memento[mementoCount - 1].saveState(cells, statistics);
        }
    }

    public boolean returnGeneration() {
        if (mementoCount != 0) {
            memento[--mementoCount].undo(cells, statistics);
            return true;
        }
        return false;
    }
}
